/*
This source file is part of the open source TripleGasm project
Copyright (C) 2010 Roma Kovalev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

For the latest info, see http://code.google.com/p/triplegasm
You can contact the author(s) via e-mail: endiannesz@gmail.com
*/

#include "core.hpp"

using namespace OgreBites;
using namespace Ogre;
using namespace OIS;
using namespace std;
using namespace TripleGasm;

Core::Core() :
mRoot(nullptr),
  mCamera(nullptr),
  mSceneMgr(nullptr),
  mWindow(nullptr),
  mResourcesCfg(StringUtil::BLANK),
  mPluginsCfg(StringUtil::BLANK),
  mTrayMgr(nullptr),
  mCursorWasVisible(true),
  mShutDown(false),
  mInputManager(nullptr),
  mMouse(nullptr),
  mKeyboard(nullptr)
{
}

Core::~Core()
{
  if (mTrayMgr) 
    delete mTrayMgr;

  // Remove this class as a Window listener
  WindowEventUtilities::removeWindowEventListener(mWindow, this);
  windowClosed(mWindow);
  delete mRoot;
}

bool Core::go()
{
  mResourcesCfg= "resources.cfg";
  mPluginsCfg  = "plugins.cfg";

  // Construct Root
  mRoot= new Root(mPluginsCfg);

  // Setup resources
  // Load resource paths from config file
  ConfigFile cf;
  cf.load(mResourcesCfg);

  // Go through all sections & settings in the file
  ConfigFile::SectionIterator seci= cf.getSectionIterator();

  String secName, typeName, archName;
  while (seci.hasMoreElements())
  {
    secName= seci.peekNextKey();
    ConfigFile::SettingsMultiMap* pSettings= seci.getNext();
    ConfigFile::SettingsMultiMap::iterator i;
    for (i = pSettings->begin(); i != pSettings->end(); ++i)
    {
      typeName= i->first;
      archName= i->second;
      ResourceGroupManager::getSingleton().addResourceLocation(
        archName, typeName, secName);
    }
  }

  // Show the configuration dialog and initialise the system
  // You can skip this and use root.restoreConfig() to load configuration
  // settings if you were sure there are valid ones saved in ogre.cfg
  if (mRoot->restoreConfig() || mRoot->showConfigDialog())
    // If returned true, user clicked OK so initialise
    // Here we choose to let the system create a default rendering window by passing 'true'
    mWindow= mRoot->initialise(true, "Ogre Render Window");
  else
    return false;

  // choose SceneManager
  // Get the SceneManager, in this case a generic one
  mSceneMgr= mRoot->createSceneManager(ST_GENERIC);

  // Create the camera
  mCamera= mSceneMgr->createCamera("PlayerCam");
  mCamera->setNearClipDistance(5);

  // Create one viewport, entire window
  Viewport* pVp= mWindow->addViewport(mCamera);
  pVp->setBackgroundColour(ColourValue(0, 0, 0));

  // Alter the camera aspect ratio to match the viewport
  mCamera->setAspectRatio(
    Real(pVp->getActualWidth()) / Real(pVp->getActualHeight()));

  // Set default mipmap level
  // Since most of the gameplay happens on a constant Z plane, no mipmaps are needed
  TextureManager::getSingleton().setDefaultNumMipmaps(0);

  // Load resources
  ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

  mSceneMgr->setAmbientLight(ColourValue(0, 0, 0));
  mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);

  // Create FrameListener
  LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
  ParamList pl;
  size_t windowHnd= 0;
  ostringstream windowHndStr;

  mWindow->getCustomAttribute("WINDOW", &windowHnd);
  windowHndStr << windowHnd;
  pl.insert(make_pair(string("WINDOW"), windowHndStr.str()));

  mInputManager= InputManager::createInputSystem(pl);

  mKeyboard= static_cast<Keyboard*>(mInputManager->createInputObject(OISKeyboard, true));
  mMouse   = static_cast<Mouse*>(mInputManager->createInputObject(OISMouse, true));

  mMouse->setEventCallback(this);
  mKeyboard->setEventCallback(this);

  // Set initial mouse clipping size
  windowResized(mWindow);

  // Register as a Window listener
  WindowEventUtilities::addWindowEventListener(mWindow, this);

  mTrayMgr= new SdkTrayManager("InterfaceName", mWindow, mMouse, this);
#ifdef _DEBUG
  mTrayMgr->toggleAdvancedFrameStats();
  mTrayMgr->showFrameStats(TL_TOPLEFT);
#endif

  mRoot->addFrameListener(this);

  MaterialManager::getSingleton().setDefaultTextureFiltering(TFO_ANISOTROPIC);
  MaterialManager::getSingleton().setDefaultAnisotropy(8);

  Create();

  mRoot->startRendering();

  return true;
}

bool Core::frameRenderingQueued(const FrameEvent& evt)
{
  if (mWindow->isClosed() || mShutDown)
    return false;

  //Need to capture/update each device
  mKeyboard->capture();
  mMouse->capture();

  mTrayMgr->frameRenderingQueued(evt);

  return true;
}

bool Core::keyPressed(const KeyEvent &arg)
{
  if (arg.key == KC_F && mTrayMgr->areFrameStatsVisible())
    mTrayMgr->hideFrameStats();
  else if (arg.key == KC_F)
    mTrayMgr->showFrameStats(TL_TOPLEFT);
  else if (arg.key == KC_SYSRQ)   // Take a screenshot
    mWindow->writeContentsToTimestampedFile("screenshot", ".jpg");
  else if (arg.key == KC_ESCAPE)
    return false; // Don't shutdown yet.

  return true;
}

bool Core::keyReleased(const KeyEvent &arg)
{
  return true;
}

bool Core::mouseMoved(const MouseEvent &arg)
{
  mTrayMgr->injectMouseMove(arg);
  return true;
}

bool Core::mousePressed(const MouseEvent &arg, MouseButtonID id)
{
  mTrayMgr->injectMouseDown(arg, id);
  return true;
}

bool Core::mouseReleased(const MouseEvent &arg, MouseButtonID id)
{
  mTrayMgr->injectMouseUp(arg, id);
  return true;
}

// Adjust mouse clipping area
void Core::windowResized(RenderWindow* rw)
{
  unsigned int width, height, depth;
  int left, top;
  rw->getMetrics(width, height, depth, left, top);

  const MouseState &ms= mMouse->getMouseState();
  ms.width = width;
  ms.height= height;
}

// Unattach OIS before window shutdown (very important under Linux)
void Core::windowClosed(RenderWindow* rw)
{
  //Only close for window that created OIS (the main window in these demos)
  if (rw == mWindow)
  {
    if (mInputManager)
    {
      mInputManager->destroyInputObject(mMouse);
      mInputManager->destroyInputObject(mKeyboard);

      InputManager::destroyInputSystem(mInputManager);
      mInputManager= 0;
    }
  }
}

void Core::Create()
{

}

void Core::reconfigure(
  const Ogre::String& renderer, 
  Ogre::NameValuePairList& options)
{
  Ogre::RenderSystem* rs= mRoot->getRenderSystemByName(renderer);

  // set all given render system options
  for (auto it= options.begin(); it != options.end(); ++it)
    rs->setConfigOption(it->first, it->second);

  mRoot->queueEndRendering(); // break from render loop
}